#include "Model.h"
#include "Graphics.h"

Model::Model(Config* config, string Name) {
	this->config = config;
	this->ID     = config->GetNewID();
	this->Name   = Name;
}

Model* Model::GetCopy() {
	Model* result       = new Model(config, Name);
	result->ID          = ID;
	result->Translation = Translation;
	result->Scaling     = Scaling;
	result->Rotation    = Rotation;
	for(UINT i=0; i<Primitives.size(); i++)
		result->Primitives.push_back(Primitives.at(i)->GetCopy());
	for(UINT i=0; i<Models.size(); i++)
		result->Models.push_back(Models.at(i)->GetCopy());
	return result;
}

boolean Model::AddModel(Model* model) {
	if(!ContainsModel(model->ID)) {
		Models.push_back(model);
		return true;
	}
	return false;
}

Model* Model::GetModel(int id) {
	if(this->ID == id) {
		return this;
	}
	for(UINT i=0; i<Models.size(); i++) {
		if(Models.at(i)->ID == id) {
			return Models.at(i);
		} else if(Models.at(i)->ContainsModel(id)) {
			return Models.at(i)->GetModel(id);
		}
	}
	assert(false); //model with ID id cannot be found
	return NULL;
}

boolean Model::RemoveModel(int id) {
	for(UINT i=0; i<Models.size(); i++) {
		if(Models.at(i)->ID == id) {
			Models.erase(Models.begin() + i);
			return true;
		} else if(Models.at(i)->ContainsModel(id)) {
			return Models.at(i)->RemoveModel(id);
		}
	}
	return false;
}

boolean Model::ContainsModel(int id) {
	for(UINT i=0; i<Models.size(); i++)
		if(Models.at(i)->ID == id || Models.at(i)->ContainsModel(id))
			return true;
	return false;
}

Primitive* Model::GetPrimitive(int id) {
	for(UINT i=0; i<Primitives.size(); i++)
		if(Primitives.at(i)->ID == id)
			return Primitives.at(i);

	for(UINT i=0; i<Models.size(); i++)
		if(Models.at(i)->ContainsPrimitive(id))
			return Models.at(i)->GetPrimitive(id);

	assert(false); //primitive with this ID cannot be found!
	return NULL; //return new Primitive(config, 0, "", "");
}

boolean Model::AddPrimitive(Primitive* prim) {
	if(!ContainsPrimitive(prim->ID)) {
		Primitives.push_back(prim);
		return true;
	}
	return false;
}

int Model::GetNrOfPrimitives() {
	UINT result = Primitives.size();
	for(UINT i=0; i<Models.size(); i++)
		result += Models.at(i)->GetNrOfPrimitives();
	return result;
}

boolean Model::RemovePrimitive(int id) {
	for(UINT i=0; i<Primitives.size(); i++) {
		if(Primitives.at(i)->ID == id) {
			Primitives.erase(Primitives.begin() + i);
			return true;
		}
	}
	for(UINT i=0; i<Models.size(); i++)
		if(Models.at(i)->RemovePrimitive(id))
			return true;
	return false;
}

boolean Model::ContainsPrimitive(int id) {
	for(UINT i=0; i<Primitives.size(); i++)
		if(Primitives.at(i)->ID == id)
			return true;
	for(UINT i=0; i<Models.size(); i++)
		if(Models.at(i)->ContainsPrimitive(id))
			return true;
	return false;
}

int Model::GetNrOfIndices() {
	int result = 0;
	for(UINT i=0; i<Primitives.size(); i++)
		result += (int)(Primitives.at(i)->textureFaces->size()*6);
	for(UINT i=0; i<Models.size(); i++)
		result += Models.at(i)->GetNrOfIndices();
	return result;
}

int Model::GetNrOfVertices() {
	int result = 0;
	for(UINT i=0; i<Primitives.size(); i++)
		result += (int)(Primitives.at(i)->textureFaces->size()*4);
	for(UINT i=0; i<Models.size(); i++)
		result += Models.at(i)->GetNrOfVertices();
	return result;
}

XMVECTOR Model::GetLowest() {
	XMVECTOR result = XMVectorSet(*(float*)std::numeric_limits<float>::max,
		                          *(float*)std::numeric_limits<float>::max,
							      *(float*)std::numeric_limits<float>::max,
								  0);
    for(UINT i=0; i<Primitives.size(); i++) {
		XMVECTOR primitiveLowest = Primitives.at(i)->GetLowest();
        if(XMVectorGetY(primitiveLowest) < XMVectorGetY(result)) {
            result = primitiveLowest;
        }
    }
    for(UINT i=0; i<Models.size(); i++) {
		XMVECTOR modelLowest = Models.at(i)->GetLowest();
        if(XMVectorGetY(modelLowest) < XMVectorGetY(result)) {
            result = modelLowest;
        }
    }
    return result;
}

/*
 * Recursively translate this model.
 * The model's own translation is contained in the Model object,
 * the model's "stacked" translation is stored in the TextureFace array in the Graphics object.
 */
void Model::Translate(XMVECTOR transl) {
	XMVECTOR v = XMVectorSet(XMVectorGetX(transl) + XMVectorGetX(Translation),
							 XMVectorGetY(transl) + XMVectorGetY(Translation),
							 XMVectorGetZ(transl) + XMVectorGetZ(Translation),
							 0.0f);
    for(UINT i=0; i<Primitives.size(); i++)
        Primitives.at(i)->Translate(v);
    for(UINT i=0; i<Models.size(); i++)
        Models.at(i)->Translate(v);
}

void Model::Scale(XMVECTOR scal) {
	XMVECTOR v = XMVectorSet(XMVectorGetX(scal) * XMVectorGetX(scal),
							 XMVectorGetY(scal) * XMVectorGetY(scal),
							 XMVectorGetZ(scal) * XMVectorGetZ(scal),
							 0.0f);
    for(UINT i=0; i<Primitives.size(); i++)
        Primitives.at(i)->Scale(v);
    for(UINT i=0; i<Models.size(); i++)
        Models.at(i)->Scale(v);
}

/*
 * See Translate.
 * rotation will 'stack' by adding up all rotations of all containing models.
 * Rotations are defined as being a value between 0 and 1. Don't process 
 * exceeding values (values greater than 1) until you arrive at the Primitives.
 */
void Model::Rotate(XMVECTOR rotat) {
	XMVECTOR v = XMVectorSet(XMVectorGetX(rotat) + XMVectorGetX(Rotation),
							 XMVectorGetY(rotat) + XMVectorGetY(Rotation),
							 XMVectorGetZ(rotat) + XMVectorGetZ(Rotation),
							 0.0f);
    for(UINT i=0; i<Primitives.size(); i++)
        Primitives.at(i)->Rotate(v);
    for(UINT i=0; i<Models.size(); i++)
        Models.at(i)->Rotate(v);
}

XMVECTOR Model::GetHighest() {
	XMVECTOR result = XMVectorSet(*(float*)std::numeric_limits<float>::min,
		                          *(float*)std::numeric_limits<float>::min,
								  *(float*)std::numeric_limits<float>::min,
								  0.0f);
	for(UINT i=0; i<Primitives.size(); i++) {
        XMVECTOR primitiveHighest = Primitives.at(i)->GetHighest();
        if(XMVectorGetY(primitiveHighest) > XMVectorGetY(result)) {
            result = primitiveHighest;
        }
    }
    for(UINT i=0; i<Models.size(); i++) {
        XMVECTOR modelHighest = Models.at(i)->GetHighest();
        if(XMVectorGetY(modelHighest) > XMVectorGetY(result)) {
            result = modelHighest;
        }
    }
    return result;
}

XMVECTOR Model::GetCenter() {
	XMVECTOR sum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
    int totalNrOfVertices = 0;

    for(UINT i=0; i<Primitives.size(); i++) {
        XMVECTOR primitiveCenter = Primitives.at(i)->GetCenter();
        int nrOfVerticesInPrimitive = Primitives.at(i)->GetNrOfVertices();

		sum = XMVectorSet(XMVectorGetX(sum) + XMVectorGetX(primitiveCenter) * nrOfVerticesInPrimitive,
						  XMVectorGetY(sum) + XMVectorGetY(primitiveCenter) * nrOfVerticesInPrimitive,
						  XMVectorGetZ(sum) + XMVectorGetZ(primitiveCenter) * nrOfVerticesInPrimitive,
						  0.0f);
        totalNrOfVertices += nrOfVerticesInPrimitive;
    }

    for(UINT i=0; i<Models.size(); i++) {
		XMVECTOR modelCenter = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
        int nrOfVerticesInModel = 0;
        GetCenter(&modelCenter, &nrOfVerticesInModel);

        //sum += modelCenter * nrOfVerticesInModel;
		sum = XMVectorSet(XMVectorGetX(sum) + XMVectorGetX(modelCenter) * nrOfVerticesInModel,
						  XMVectorGetY(sum) + XMVectorGetY(modelCenter) * nrOfVerticesInModel,
						  XMVectorGetZ(sum) + XMVectorGetZ(modelCenter) * nrOfVerticesInModel,
						  0.0f);
        totalNrOfVertices += nrOfVerticesInModel;
    }
    return sum/(float)totalNrOfVertices;
}

/*
 * This extra function was added so the user may call GetCenter() without any 
 * parameters to get the center of a model.
 */
void Model::GetCenter(XMVECTOR* sum, int* totalNrOfVertices) {
    for(UINT i=0; i<Primitives.size(); i++) {
        XMVECTOR primitiveCenter = Primitives.at(i)->GetCenter();
        int nrOfVerticesInPrimitive = Primitives.at(i)->GetNrOfVertices();

        //*sum += primitiveCenter * nrOfVerticesInPrimitive;
		*sum = XMVectorSet(XMVectorGetX(*sum) + XMVectorGetX(primitiveCenter) * nrOfVerticesInPrimitive,
						   XMVectorGetY(*sum) + XMVectorGetY(primitiveCenter) * nrOfVerticesInPrimitive,
						   XMVectorGetZ(*sum) + XMVectorGetZ(primitiveCenter) * nrOfVerticesInPrimitive,
						   0.0f);
        *totalNrOfVertices += nrOfVerticesInPrimitive;
    }
    for(UINT i=0; i<Models.size(); i++) {
		XMVECTOR modelCenter = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
        int nrOfVerticesInModel = 0;
        GetCenter(&modelCenter, &nrOfVerticesInModel);

        //*sum += modelCenter * nrOfVerticesInModel;
		*sum = XMVectorSet(XMVectorGetX(*sum) + XMVectorGetX(modelCenter) * nrOfVerticesInModel,
						   XMVectorGetY(*sum) + XMVectorGetY(modelCenter) * nrOfVerticesInModel,
						   XMVectorGetZ(*sum) + XMVectorGetZ(modelCenter) * nrOfVerticesInModel,
						   0.0f);
        *totalNrOfVertices += nrOfVerticesInModel;
    }
}
